<!DOCTYPE html>
<html>
  <head>
    <title>
      audiobuffersource-loop-comprehensive.html
    </title>
    <script src="../../resources/testharness.js"></script>
    <script src="../../resources/testharnessreport.js"></script>
    <script src="../resources/audit-util.js"></script>
    <script src="../resources/audit.js"></script>
    <script src="../resources/audiobuffersource-testing.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      let audit = Audit.createTaskRunner();

      // The following test cases assume an AudioBuffer of length 8 whose PCM
      // data is a linear ramp, 0, 1, 2, 3,... |description| is optional and
      // will be computed from the other parameters. |offsetFrame| is optional
      // and defaults to 0.

      let tests = [

        {
          description:
              'loop whole buffer by default with loopStart == loopEnd == 0',
          loopStartFrame: 0,
          loopEndFrame: 0,
          renderFrames: 16,
          playbackRate: 1,
          expected: [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7]
        },

        {
          description: 'loop whole buffer explicitly',
          loopStartFrame: 0,
          loopEndFrame: 8,
          renderFrames: 16,
          playbackRate: 1,
          expected: [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7]
        },

        {
          description: 'loop from middle to end of buffer',
          loopStartFrame: 4,
          loopEndFrame: 8,
          renderFrames: 16,
          playbackRate: 1,
          expected: [0, 1, 2, 3, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7]
        },

        {
          description: 'loop from start to middle of buffer',
          loopStartFrame: 0,
          loopEndFrame: 4,
          renderFrames: 16,
          playbackRate: 1,
          expected: [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]
        },

        {
          loopStartFrame: 4,
          loopEndFrame: 6,
          renderFrames: 16,
          playbackRate: 1,
          expected: [0, 1, 2, 3, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5]
        },

        {
          loopStartFrame: 3,
          loopEndFrame: 7,
          renderFrames: 16,
          playbackRate: 1,
          expected: [0, 1, 2, 3, 4, 5, 6, 3, 4, 5, 6, 3, 4, 5, 6, 3]
        },

        {
          loopStartFrame: 4,
          loopEndFrame: 6,
          renderFrames: 16,
          playbackRate: 0.5,
          expected:
              [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 4, 4.5, 5, 5.5]
        },

        {
          loopStartFrame: 4,
          loopEndFrame: 6,
          renderFrames: 16,
          playbackRate: 1.5,
          expected:
              [0, 1.5, 3, 4.5, 4, 5.5, 5, 4.5, 4, 5.5, 5, 4.5, 4, 5.5, 5, 4.5]
        },

        // Offset past loop end, so playback starts at loop start
        {
          loopStartFrame: 2,
          loopEndFrame: 5,
          renderFrames: 16,
          playbackRate: 1,
          offsetFrame: 6,
          expected: [2, 3, 4, 2, 3, 4, 2, 3, 4, 2, 3, 4, 2, 3, 4, 2]
        },

        // Offset before loop start, so start at offset and continue
        {
          loopStartFrame: 3,
          loopEndFrame: 6,
          renderFrames: 16,
          playbackRate: 1,
          offsetFrame: 1,
          expected: [1, 2, 3, 4, 5, 3, 4, 5, 3, 4, 5, 3, 4, 5, 3, 4]
        },

        // Offset between loop start and loop end, so start at offset and
        // continue
        {
          loopStartFrame: 3,
          loopEndFrame: 6,
          renderFrames: 16,
          playbackRate: 1,
          offsetFrame: 4,
          expected: [4, 5, 3, 4, 5, 3, 4, 5, 3, 4, 5, 3, 4, 5, 3, 4]
        },

        {
          description: 'illegal playbackRate of 47 greater than loop length',
          loopStartFrame: 4,
          loopEndFrame: 6,
          renderFrames: 16,
          playbackRate: 47,
          expected: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        },

        // Try illegal loop-points - they should be ignored and we'll loop the
        // whole buffer.

        {
          description: 'illegal loop: loopStartFrame > loopEndFrame',
          loopStartFrame: 7,
          loopEndFrame: 3,
          renderFrames: 16,
          playbackRate: 1,
          expected: [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7]
        },

        {
          description: 'illegal loop: loopStartFrame == loopEndFrame',
          loopStartFrame: 3,
          loopEndFrame: 3,
          renderFrames: 16,
          playbackRate: 1,
          expected: [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7]
        },

        {
          description: 'illegal loop: loopStartFrame < 0',
          loopStartFrame: -8,
          loopEndFrame: 3,
          renderFrames: 16,
          playbackRate: 1,
          expected: [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7]
        },

        {
          description: 'illegal loop: loopEndFrame > bufferLength',
          loopStartFrame: 0,
          loopEndFrame: 30000,
          renderFrames: 16,
          playbackRate: 1,
          expected: [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7]
        },

        // Start a loop with a duration longer than the buffer.  The output
        // should be the data from frame 1 to 6, and then looping from 3 to 5
        // until 20 frames have been played.
        {
          description: 'loop from 3 -> 6 with offset 1 for 20 frames',
          loopStartFrame: 3,
          loopEndFrame: 6,
          playbackRate: 1,
          offsetFrame: 1,
          renderFrames: 30,
          durationFrames: 20,
          expected: [
            1, 2, 3, 4, 5, 3, 4, 5, 3, 4, 5, 3, 4, 5, 3,
            4, 5, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
          ]
        },

        // Start a loop with a duration less than the length of the looping
        // frames.  The output should be the data from frame 1 to 3, and then
        // stopping because duration = 3
        {
          description: 'loop from 3 -> 8 with offset 1 for 3 frames',
          loopStartFrame: 3,
          loopEndFrame: 8,
          playbackRate: 1,
          offsetFrame: 1,
          durationFrames: 3,
          renderFrames: 30,
          expected: [
            1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
          ]
        },

        // Start a loop with a duration less than the length of the looping
        // frames.  The output should be the data from frame 1 to 3, and then
        // stopping because duration = 3
        {
          description: 'loop from 3 -> 8 with offset 7 for 3 frames',
          loopStartFrame: 3,
          loopEndFrame: 8,
          playbackRate: 1,
          offsetFrame: 7,
          durationFrames: 3,
          renderFrames: 30,
          expected: [
            7, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
          ]
        }

      ];

      let sampleRate = 32768;
      let buffer;
      let bufferFrameLength = 8;
      let testSpacingFrames = 32;
      let testSpacingSeconds = testSpacingFrames / sampleRate;
      let totalRenderLengthFrames = tests.length * testSpacingFrames;

      function runLoopTest(context, testNumber, test, should) {
        let source = context.createBufferSource();

        source.buffer = buffer;
        source.playbackRate.value = test.playbackRate;
        source.loop = true;
        source.loopStart = test.loopStartFrame / context.sampleRate;
        source.loopEnd = test.loopEndFrame / context.sampleRate;

        let offset =
            test.offsetFrame ? test.offsetFrame / context.sampleRate : 0;

        source.connect(context.destination);

        // Render each test one after the other, spaced apart by
        // testSpacingSeconds.
        let startTime = testNumber * testSpacingSeconds;

        // If durationFrames is given, run the test for the specified duration.
        if (test.durationFrames) {
          if (!test.renderFrames) {
            throw(
                'renderFrames is required for test ' + testNumber + ': ' +
                test.description);
          } else {
            if (test.durationFrames > testSpacingFrames ||
                test.durationFrames < 0) {
              throw(
                  'Test ' + testNumber + ': durationFrames (' +
                  test.durationFrames + ') outside the range [0, ' +
                  testSpacingFrames + ']');
            }
            source.start(
                startTime, offset, test.durationFrames / context.sampleRate);
          }
        } else if (test.renderFrames) {
          let duration = test.renderFrames / context.sampleRate;
          if (test.renderFrames > testSpacingFrames || test.renderFrames < 0) {
            throw(
                'Test ' + testNumber + ': renderFrames (' + test.renderFrames +
                ') outside the range [0, ' + testSpacingFrames + ']');
          }
          source.start(startTime, offset);
          source.stop(startTime + duration);
        } else {
          throw(
              'Test ' + testNumber +
              ' must specify renderFrames and possibly durationFrames');
        }
      }

      audit.define('AudioBufferSource looping test', function(task, should) {
        // Create offline audio context.
        let context =
            new OfflineAudioContext(1, totalRenderLengthFrames, sampleRate);
        buffer = createTestBuffer(context, bufferFrameLength);

        should(function() {
          for (let i = 0; i < tests.length; ++i)
            runLoopTest(context, i, tests[i], should);
        }, 'Generate ' + tests.length + ' test cases').notThrow();

        context.startRendering().then(function(audioBuffer) {
          checkAllTests(audioBuffer, should);
          task.done();
        });
      });

      audit.run();
    </script>
  </body>
</html>
